import copy
import datetime
import logging
import random
from logging import Logger

from simpy import Environment

from task_service.task_context import EquInfo, WorkStationInfo, StorerDeliverLayer
from util.dateutil import ts_date, ts_str, date_str
from x_pc.core.core import WareHouse
from x_pc.core.resource_config import ResourceType
from x_pc.core.system_context import SysDataCtx
from x_pc.db.command.model import CommandRoute
from x_pc.db.command.service import CommandResourceService
from x_pc.db.common.service import WaveletOrderService, VehicleTaskService
from x_pc.db.kwms.model import KwmsWorkQueue, KwmsWorkPool
from x_pc.db.kwms.service import KwmsService
from x_pc.db.sf_data import get_sys_init_data, get_prev_vehicle_port, WorkCalendarService, Sku, \
    get_statistical_factory_port, OrderDetailService, OrderDetail, get_zk_prev_vehicle_port
from x_pc.db.source.model import DeliveryWorkQueue
from x_pc.db.source.service import DeliveryWorkService, InteriorWorkService, PreworkWorkService, TallyWorkService
from x_pc.helpers import find_free_time
from x_pc.model.model import TaskProcess, TaskSource, Task, TaskStatus, PackType, DeviceType, CommandTypeCode


def check_inwork(time: float, work_calendar: list[tuple[float, float]]) -> bool:
    """
    判断当前时间点，是否在 工厂日历 范围内
    """
    for work_cal in work_calendar:
        if work_cal[0] <= time < work_cal[1]:
            return True
    return False


def schedule_task(task_list: list[Task], w: WareHouse, env: Environment, sys_data: SysDataCtx):
    """
    按 装车开始时间 推送任务
    并且划分 一定的 时间间隔 (当存在不同的装车时间时)
    :param task_list:
    :param w:
    :param env:
    :param sys_data:
    :return:
    """
    # 移除PTX任务
    task_list = [_ for _ in task_list if not _.ext_ptx]
    # 移除Reassign任务
    task_list = [_ for _ in task_list if not _.ext_reassign_time]
    # 移除Urgent任务
    task_list = [_ for _ in task_list if not _.is_urgent]
    # task_list 按 vehicle_task_begin_time 有序
    for task in task_list:
        if task.ext_source == TaskSource.INTERIOR:
            continue
        while True:
            task_time = ts_str(task.vehicle_task_begin_time.timestamp()) if task.vehicle_task_begin_time else None
            # 无vehicle_task_begin_time时，直接分发任务
            # 校验工作时间
            if check_inwork(env.now, sys_data.work_calendar) and not task.vehicle_task_begin_time:
                debug_msg = f'env: {env.now}, 分发任务 [即时] task: {task.task_id}, 任务时间: {task_time}, 当前时间: {ts_str(env.now)}'
                sys_data.logger.debug(debug_msg)
                w.push_task(task)
                break
            # 校验工作时间
            fixed_vehicle_task_time = task.vehicle_task_begin_time.timestamp() - sys_data.advance_vehicle_time
            if check_inwork(env.now, sys_data.work_calendar) and env.now >= fixed_vehicle_task_time:
                debug_msg = f'env: {env.now}, 分发任务 [按时] task: {task.task_id}, 任务时间: {task_time}, 当前时间: {ts_str(env.now)}, 提前量: {sys_data.advance_vehicle_time / 60}'
                sys_data.logger.debug(debug_msg)
                w.push_task(task)
                break
            else:
                # 无任务时，递进1s等待
                yield env.timeout(1)


def schedule_task_urgent(task_list: list[Task], w: WareHouse, env: Environment, sys_data: SysDataCtx):
    """
    分配紧急任务 只要是(减去提前时间)工作时间段 就分发任务
    """
    task_list = [_ for _ in task_list if _.is_urgent and not _.ext_reassign_time]  # 急料也会有未匹配到小波次场景
    for task in task_list:
        # 不在工作时间 等待
        while not check_inwork(env.now, sys_data.work_calendar):
            yield env.timeout(1)
        while True:
            if check_inwork(env.now, sys_data.work_calendar):
                debug_msg = f'env: {env.now}, 分发任务 [Urgent] task: {task.task_id}, 当前时间: {ts_str(env.now)}'
                sys_data.logger.debug(debug_msg)
                w.push_task(task)
                break
            else:
                yield env.timeout(1)


def schedule_task_ptx2(task_list: list[Task], w: WareHouse, env: Environment, sys_data: SysDataCtx):
    task_list = [_ for _ in task_list if _.ext_ptx]
    for task in task_list:
        # 不在工作时间 等待
        while not check_inwork(env.now, sys_data.work_calendar):
            yield env.timeout(1)
        while True:
            # 按PTX时间 分发任务。当没有PTX时间时，直接分发 (列表本身有序)
            if check_inwork(env.now, sys_data.work_calendar) and (
                    not task.ext_ptx_time or env.now >= task.ext_ptx_time):
                task_time = ts_str(task.vehicle_task_begin_time.timestamp()) if task.vehicle_task_begin_time else None
                debug_msg = f'env: {env.now}, 分发任务 [PTX] task: {task.task_id}, 任务时间: {task_time}, 当前时间: {ts_str(env.now)}'
                sys_data.logger.debug(debug_msg)
                w.push_task(task)
                break
            else:
                yield env.timeout(1)


def schedule_task_reassign(task_list: list[Task], w: WareHouse, env: Environment, sys_data: SysDataCtx):
    """
    强制指定 分发时间
    """
    task_list = [_ for _ in task_list if _.ext_reassign_time]
    for task in task_list:
        # 不在工作时间 等待
        while not check_inwork(env.now, sys_data.work_calendar):
            yield env.timeout(1)
        while True:
            # 按PTX时间 分发任务。当没有PTX时间时，直接分发 (列表本身有序)
            if check_inwork(env.now, sys_data.work_calendar) and (env.now >= task.ext_reassign_time):
                task_time = ts_str(task.vehicle_task_begin_time.timestamp()) if task.vehicle_task_begin_time else None
                debug_msg = f'env: {env.now}, 分发任务 [REASSIGN] task: ({task.ext_source.value}) {task.task_id}, 任务时间: {task_time}, 当前时间: {ts_str(env.now)}'
                sys_data.logger.debug(debug_msg)

                w.push_task(task)
                break
            else:
                yield env.timeout(1)


def watch_tasks_to_stop(task_list: list[Task], w: WareHouse, env: Environment, sys_data: SysDataCtx):
    """
    监控 任务全部完成时  提前?退出执行
    """
    while True:
        unfiniszhed_tasks = [_ for _ in task_list if _.run_status != TaskStatus.DONE]
        if not unfiniszhed_tasks:
            w.stop()
            sys_data.logger.info('[PC] 任务全部完成 停止运行')
            break
        else:
            yield env.timeout(1)


def print_task(task: Task, logger: logging.Logger):
    info_msg = (f'[PC] \t--> source: {task.ext_source.name}, task_id: {task.task_id}, status: {task.run_status}, '
                f'route: ({task.ext_route.id}){task.ext_route.name}, time: {ts_str(task.start_time)} -> {ts_str(task.end_time)}')
    logger.info(info_msg)


def run_task_list(task_list: list[Task], sys_data: SysDataCtx, logger: Logger) -> list[Task]:
    """
    执行任务
    :param logger:
    :param task_list: 任务数据
    :param sys_data: 系统暂存数据
    :return:
    """
    if not task_list:
        logger.warning('[PC] 排程母任务数据 未找到，任务中止。')
        return []

    base_time = sys_data.base_time
    # 环境
    env = Environment(initial_time=base_time)
    wh = WareHouse(env, '仓库', sys_data)
    #
    sys_data.ware_house = wh
    # 推送任务
    env.process(schedule_task_urgent(task_list, wh, env, sys_data))
    env.process(schedule_task(task_list, wh, env, sys_data))
    env.process(schedule_task_ptx2(task_list, wh, env, sys_data))
    env.process(schedule_task_reassign(task_list, wh, env, sys_data))
    # 监测 任务是否全部完成。终止任务
    env.process(watch_tasks_to_stop(task_list, wh, env, sys_data))
    # 运行
    env.process(wh.run())
    # 启动
    wh.start()
    env.run(until=base_time + sys_data.sim_time_limit)
    #
    wh.stop()
    # 获取最终处理结果
    logger.info('[PC] 任务处理完成: ')
    for task in task_list:
        if task.is_master_dc_task:
            print_task(task, logger)
            for ot in task.ext_group_tasks[1:]:
                ot: Task
                # 直接使用Master任务状态 修正从任务状态
                if ot.run_status != task.run_status:
                    info_msg = f'[PC] \t\t根据主任务状态 修正从任务状态 task: {ot.task_id} {ot.run_status} -> {task.run_status}'
                    logger.info(info_msg)
                    ot.run_status = task.run_status
                print_task(ot, logger)
        elif task.is_master_dc_task is None:
            print_task(task, logger)
    return task_list


def gen_taskprocess_with_commandroute(route: CommandRoute, sys_data: SysDataCtx) -> list[TaskProcess]:
    """
    根据工序路径 生成 任务流程
    :param sys_data:
    :param route:
    :return:
    """
    if not route:
        return []
    process_list: list[TaskProcess] = []
    order = 1
    for step in route.ext_step_list:
        resource_type = ResourceType(step.ext_resource_type)
        tp = TaskProcess(None, None, order, None, None, None)
        tp.ext_step = step
        tp.ext_resource_type = resource_type
        work_type = sys_data.get_work_type_by_id(step.ext_work_type)
        if not work_type:
            route.ext_isvalid = False
            route.ext_remarks = f'子步骤WorkType缺失, work_type: {step.ext_work_type}'
            warning_msg = f'[PC] 路径不可用(入参) --> route: ({route.id}) {route.name}, reason: {route.ext_remarks}'
            sys_data.logger.warning(warning_msg)
            return []
        tp.ext_work_type = work_type
        order += 1
        process_list.append(tp)
    return process_list


def mdc_rtkey(rtask: DeliveryWorkQueue) -> tuple:
    if rtask.origin_pack_type == PackType.ZZX.value and rtask.target_pack_type == PackType.ZZX.value:
        return rtask.command_group_code, rtask.lpn, rtask.deliver_factory_code
    return rtask.command_group_code, rtask.lpn


def mdc_key(task: Task) -> tuple:
    if task.origin_pack_type == PackType.ZZX.value and task.target_pack_type == PackType.ZZX.value:
        return task.ext_source.value, task.lpn, task.deliver_factory_code
    return task.ext_source.value, task.lpn


def process_designate_outlet(storer_layer: StorerDeliverLayer, t: Task, crs: CommandResourceService):
    """
    处理 指定出口
    当指定出货口 非当前路径楼层时
    - 若不存在，则任务失败
    - 若存在，则标识Task 并继续
    """
    # 当不需要指定时，忽略
    if not crs.sys_data.is_designate_outlet:
        return
    # 没有出口的任务，忽略
    if not t.ext_route or not t.ext_route.ext_ch_port:
        return
    designate_outlets = []
    designate_equtype = None
    for do in storer_layer.designate_outlets:
        device = crs.sys_data.unit_code_data_dict.get(do)
        if not device:
            continue
        designate_equtype = device['equ_type']
        valid = crs.sys_data.is_valid_device_raw(ResourceType.UNIT.value, designate_equtype, do)
        if valid:
            designate_outlets.append(do)
    # 无法确认 指定出口类型，忽略
    if not designate_equtype:
        return
    # 回写 指定的出口类型
    storer_layer.designate_equtype = designate_equtype
    # 修正 指定的出口 (移除不可用的设备)
    storer_layer.designate_outlets = designate_outlets
    # 当指定的出口非当前路径楼层时
    if t.ext_route.ext_ch_port != designate_equtype:
        t.ext_designate_outlets = designate_outlets
        t.ext_designate_equtype = designate_equtype
        crs.sys_data.logger.debug('[PC] 夸跨楼层指定出口 任务: %s, 路径设备类型: %s, 指定设备类型: %s, 指定出口: %s',
                                  t.task_id, t.ext_route.ext_ch_port, designate_equtype, designate_outlets)


def fix_task_list(raw_task_list: list[DeliveryWorkQueue], crs: CommandResourceService, logger: Logger) -> list[Task]:
    """
    转换任务列表
    - 将其转换为Task
    - 补充子任务
    :param logger:
    :param raw_task_list:
    :param crs:
    :return:
    """
    task_list: list[Task] = []
    # 多DC场景任务
    lpn_nums_dict: dict[tuple, list[str]] = dict()
    lpn_factory_nums_dict: dict[tuple[str, str, str], list[str]] = dict()
    for task in raw_task_list:
        # 只有 origin_sku_num < act_num 时，才是多DC任务
        if task.origin_sku_num < task.act_num:
            group_code = task.command_group_code
            # 仅发货/在库 多DC场景
            if group_code not in (TaskSource.DELIVERY.value, TaskSource.INTERIOR.value):
                continue
            #
            rt_key = mdc_rtkey(task)
            if rt_key not in lpn_nums_dict:
                lpn_nums_dict[rt_key] = []
            lpn_nums_dict[rt_key].append(task.task_id)
            #
            lpn_factory_key = (group_code, task.lpn, task.deliver_factory_code)
            if lpn_factory_key not in lpn_factory_nums_dict:
                lpn_factory_nums_dict[lpn_factory_key] = []
            lpn_factory_nums_dict[lpn_factory_key].append(task.task_id)
    # dc_lpn: list[tuple] = [_ for _ in lpn_nums_dict if len(lpn_nums_dict[_]) > 1]
    # for lpn in [lpn for lpn in lpn_nums_dict if lpn not in dc_lpn]:
    #     del lpn_nums_dict[lpn]
    info_msg = f'[多DC] INIT lpn: {lpn_nums_dict}'
    logger.info(info_msg)
    # 查找 多DC场景，但是总PCS数 不等于 act_num的任务。标识其需要GTP拣选
    for vs in lpn_nums_dict.values():
        vs_list = [_ for _ in raw_task_list if _.task_id in vs]
        if vs_list:
            rt_1 = vs_list[0]
            # 发货 / 在库
            if rt_1.command_group_code not in (TaskSource.DELIVERY.value, TaskSource.INTERIOR.value):
                continue
            act_num = rt_1.act_num
            sum_target_num = sum([_.target_sku_num for _ in vs_list])
            if act_num != sum_target_num:
                for vt in vs_list:
                    # 标识 强制GTP
                    vt.ext_force_gtp = True

    # 修正 栈板、纸箱 直发场景
    # 起始包规为[1栈板、2纸箱]，[同一交付对象 + (SUM(target_pcs) == act_num)] 时，最终包规为[1栈板、2纸箱]
    dc_lpns_list = [_ for _ in lpn_nums_dict.values()]
    # 无需修正包规的任务 (类型+ID)
    do_not_fix_pack_type_set: set[tuple[str, str]] = set()
    # 全量多DC
    all_dc_lpns_set: set[tuple[str, str]] = set()
    all_dc_lpns_dict: dict[tuple[str, str], list[tuple[str, str, int]]] = dict()
    for dc_lpns in dc_lpns_list:
        dc_lpn_tasks = [_ for _ in raw_task_list if _.task_id in dc_lpns]
        for dc_task in dc_lpn_tasks:
            all_dc_lpns_set.add((dc_task.command_group_code, dc_task.lpn))
            dc_lpns_dict_value = [(_.command_group_code, _.lpn, _.target_sku_num) for _ in dc_lpn_tasks]
            all_dc_lpns_dict[(dc_task.command_group_code, dc_task.lpn)] = dc_lpns_dict_value
    #
    for dc_lpns in dc_lpns_list:
        dc_lpn_tasks = [_ for _ in raw_task_list if _.task_id in dc_lpns]
        # 确保 起始包规为 1栈板/2纸箱
        if dc_lpn_tasks[0].origin_pack_type not in (PackType.ZB.value, PackType.ZX.value):
            continue
        # 保证为 只有一个交付对象
        if len(set([_.deliver_factory_code for _ in dc_lpn_tasks])) > 1:
            continue
        # 确保 PCS 数相同
        if dc_lpn_tasks[0].act_num != sum([_.target_sku_num for _ in dc_lpn_tasks]):
            continue
        # 当最终包规 与 起始包规不相同时，修正
        for dc_task in dc_lpn_tasks:
            # 是否为自制 及自制层
            storer_layer = crs.sys_data.store_layer_dict.get(dc_task.deliver_factory_code)
            if dc_task.origin_pack_type == PackType.ZX.value and storer_layer and storer_layer.is_zz() and storer_layer.is_b1():
                continue
            #
            if dc_task.origin_pack_type == PackType.ZX.value:
                do_not_fix_pack_type_set.add((dc_task.command_group_code, dc_task.task_id))
            if dc_task.target_pack_type != dc_task.origin_pack_type:
                warning_msg = f'[PC] 任务包规修正 (多DC {dc_task.origin_pack_type} 直发) task: {dc_task.task_id} target_pack_type: {dc_task.target_pack_type} -> {dc_task.origin_pack_type}'
                logger.warning(warning_msg)
                dc_task.target_pack_type = dc_task.origin_pack_type
    # 常规任务
    for rt in raw_task_list:
        # No Split 任务，修正 PCS数 (仅2,3修正 2024-8-8)
        if Sku.gen_uk(rt.sku) in crs.sys_data.nosplit_skus:
            if rt.origin_pack_type in (PackType.ZX.value, PackType.ZZX.value):
                if rt.target_sku_num != rt.origin_sku_num:
                    # 实发实 无需修正
                    if rt.deliver_factory != 'REAL':
                        debug_msg = f'[PC] PCS数修正 (NOSPLIT场景) {rt.target_sku_num} -> {rt.origin_sku_num}'
                        logger.debug(debug_msg)
                        rt.target_sku_num = rt.origin_sku_num

        # 是否为自制 及自制层
        storer_layer = crs.sys_data.store_layer_dict.get(rt.deliver_factory_code)

        # 获取路径
        command_route = crs.get_command_route(rt, crs.sys_data.nosplit_skus, crs.sys_data.split_skus, storer_layer,
                                              do_not_fix_pack_type_set, all_dc_lpns_set, all_dc_lpns_dict)
        if not command_route:
            error_msg = f'[PC] 找不到可用路径。task: {rt.task_id}'
            logger.error(error_msg)
            continue
        process_list = gen_taskprocess_with_commandroute(command_route, crs.sys_data)
        if not process_list:
            warning_msg = f'[PC] 缺少任务步骤：RouteId: {command_route.id}, Name: {command_route.name}'
            logger.warning(warning_msg)
        t = Task(rt.task_id, None, None, None, None, rt.vehicle_task_begin_time, rt.time_window_start_time,
                 rt.wops_vehicle_taskid, rt.origin_loc, process_list)
        t.ext_route = command_route
        # 任务来源(类型) 发货、在库、预作业、理货
        t.ext_source = TaskSource(rt.command_group_code)
        # 交付厂家编码
        t.deliver_factory_code = rt.deliver_factory_code
        # 包装类型、PCS数
        t.origin_pack_type = rt.origin_pack_type
        t.target_pack_type = rt.target_pack_type
        t.origin_sku_num = rt.origin_sku_num
        t.target_sku_num = rt.target_sku_num
        # 拆包相关(MPQ、件箱、栈板 PCS数)
        t.ext_innerpack, t.ext_casecant, t.ext_act_num = rt.innerpack, rt.casecant, rt.act_num
        t.lpn = rt.lpn
        t.sku = rt.sku
        for attr in ('cwmsinvtaskdetil', 'consigneekey'):
            if hasattr(rt, attr):
                setattr(t, attr, getattr(rt, attr))
        # 多DC
        rt_key = mdc_key(t)
        if rt_key in lpn_nums_dict:
            t.ext_group_id = lpn_nums_dict[rt_key][0][-64:]
        lpn_factory_key = (t.ext_source.value, t.lpn, t.deliver_factory_code)
        if lpn_factory_key in lpn_factory_nums_dict:
            if len(lpn_factory_nums_dict[lpn_factory_key]) > 1:
                t.ext_factory_group_id = lpn_factory_nums_dict[lpn_factory_key][0][-64:]
        if hasattr(rt, 'ext_force_gtp'):
            t.ext_force_gtp = getattr(rt, 'ext_force_gtp')
        # 设置一下 在库任务 出货口设备类型
        if rt.command_type_code in crs.sys_data.inv_isolated_task:
            if rt.consigneekey:
                device = crs.sys_data.unit_code_data_dict.get(rt.consigneekey)
                if device:
                    t.ext_zk_equ_type = device['equ_type']
            else:
                t.ext_zk_equ_type = t.ext_route.ext_zk_ports[0] if t.ext_route and t.ext_route.ext_zk_ports else None
        # ptx
        if rt.template_id == 'PTX':
            t.ext_ptx = True
        # 实发实
        if rt.deliver_factory and rt.deliver_factory == 'REAL':
            t.ext_delivery_real = True
        # 处理下 指定出货口(发货)任务
        if t.ext_source == TaskSource.DELIVERY:
            process_designate_outlet(storer_layer, t, crs)
        #
        task_list.append(t)
    # 多DC 回写Task的ext_group_tasks
    for task in task_list:
        rt_key = mdc_key(task)
        if rt_key in lpn_nums_dict:
            if not (task.origin_sku_num < task.ext_act_num):
                error_msg = f'[PC] 多DC任务异常 非多DC任务 task: {task.task_id}, 存在多DC数据: {rt_key}'
                logger.error(error_msg)
                task.ext_group_tasks = [task]
                continue
            task.ext_group_tasks = [_ for _ in task_list if _.task_id in lpn_nums_dict[rt_key]]
            task.ext_group_tasks.sort(key=lambda _: _.ext_route.is_mpq, reverse=True)
    # 数据修正
    for task in task_list:
        # 仅修正 发货任务
        if task.ext_source != TaskSource.DELIVERY:
            continue
        # EMS(非自制) 纸箱 部分场景(多DC可直发) 无需修正
        if task.origin_pack_type == PackType.ZX.value:
            storer_layer = crs.sys_data.store_layer_dict.get(task.deliver_factory_code)
            if storer_layer.is_zz():
                continue
            task_dc_lpn = (task.ext_source.value, task.lpn)
            if task_dc_lpn in do_not_fix_pack_type_set:
                continue
        # 针对件箱多DC场景(非NOSLPIT) 2->2, 修正出包规, 需要设置为2->3 才能正确选择路径
        # 不拆包sku 去一楼的 最终形态会是2 其他都是3
        if task.origin_pack_type == PackType.ZX.value and task.target_pack_type == PackType.ZX.value and task.is_master_dc_task is not None:
            # 非-1自制 或者 (自制1/EMS)不在NOSPLIT列表中
            if (task.ext_route.is_zz() and task.ext_route) or Sku.gen_uk(task.sku) not in crs.sys_data.nosplit_skus:
                # 部分无需 修正包规
                if (task.ext_source.value, task.task_id) in do_not_fix_pack_type_set:
                    continue
                if task.target_pack_type != PackType.ZZX.value:
                    warning_msg = f'[PC] 路径包规修正 (多DC场景 非-1或其他不在NOSPLIT) task: {task.task_id}, target_pack_type: {task.target_pack_type} -> {PackType.ZZX.value}'
                    logger.warning(warning_msg)
                    task.target_pack_type = PackType.ZZX.value

                er = task.ext_route
                routes = crs.get_command_routes(er.command_type_code, er.start_pack_type, task.target_pack_type,
                                                er.is_mpq, er.remark)
                task.ext_route = random.choice(routes)
                task.process_list = gen_taskprocess_with_commandroute(task.ext_route, crs.sys_data)
                debug_msg = f'[PC] 路径选择修正 (多DC 最终包规) task: {task.task_id}, route: ({task.ext_route.id}){task.ext_route.name}'
                logger.debug(debug_msg)
    for task in task_list:
        rt_key = mdc_key(task)
        if rt_key in lpn_nums_dict:
            # 处理非 Master DC场景 (需要让他们都走ZCB或者ZHB  同一个设备)
            if task.is_master_dc_task:
                task_equ_list = [_.ext_equ_type[0] for _ in task.ext_route.ext_step_list]
                equ_type = None
                if DeviceType.ZCB.value in task_equ_list:
                    equ_type = DeviceType.ZCB.value
                elif DeviceType.ZHB.value in task_equ_list:
                    equ_type = DeviceType.ZHB.value
                for ot in task.ext_group_tasks[1:]:
                    er = ot.ext_route
                    if task.ext_route is er:
                        # 若路径相同 则跳过
                        continue
                    er_has_out = er.has_out == 1
                    routes = crs.get_command_routes(er.command_type_code, er.start_pack_type, er.end_pack_type,
                                                    er.is_mpq, er.remark)
                    for route in routes:
                        if er_has_out and route.has_out != 1:
                            continue
                        if equ_type in [_.ext_equ_type[0] for _ in route.ext_step_list]:
                            if ot.ext_route != route:
                                ot.ext_route = route
                                ot.process_list = gen_taskprocess_with_commandroute(route, crs.sys_data)
                                debug_msg = f'[PC] 路径选择修正 (多DC) ZCB/ZHB task: {ot.task_id}, route: ({ot.ext_route.id}){ot.ext_route.name}'
                                logger.debug(debug_msg)
    for task in task_list:
        rt_key = mdc_key(task)
        if rt_key in lpn_nums_dict and len(lpn_nums_dict[rt_key]) > 1:
            if task.is_master_dc_task:
                # 判断当前组是否有GTP设备，如有的话，则需要把没有GTP设备的路径  都更换为有GTP设备的路径
                has_gtp = False
                for gt in task.ext_group_tasks:
                    if DeviceType.GTP.value in [_.ext_equ_type[0] for _ in gt.ext_route.ext_step_list]:
                        has_gtp = True
                        break
                if has_gtp:
                    for gt in task.ext_group_tasks:
                        if DeviceType.GTP.value in [_.ext_equ_type[0] for _ in gt.ext_route.ext_step_list]:
                            continue
                        # 需要修改 任务路径走GTP的任务
                        er = gt.ext_route
                        routes = crs.get_command_routes(er.command_type_code, er.start_pack_type, er.end_pack_type,
                                                        '1', er.remark)
                        # 默认直接选择第一个
                        if not routes:
                            error_msg = (f'[PC] 路径选择修正 多DC有GTP场景 找不到路径。'
                                         f'command: {er.command_type_code}, '
                                         f'pack: {er.start_pack_type}->{er.end_pack_type}, remark: {er.remark}, IS_MPQ')
                            logger.error(error_msg)
                            continue
                        route = routes[0]
                        gt.ext_route = route
                        gt.process_list = gen_taskprocess_with_commandroute(route, crs.sys_data)
                        debug_msg = f'[PC] 路径选择修正 (多DC) GTP task: {gt.task_id}, route: ({gt.ext_route.id}){gt.ext_route.name}'
                        logger.debug(debug_msg)

    return task_list


def filter_task_list(raw_task_list: list[DeliveryWorkQueue], crs: CommandResourceService, logger: Logger) -> list[
    DeliveryWorkQueue]:
    """
    过滤一下任务
    """
    task_list = []
    for task in raw_task_list:
        if task.command_type_code == CommandTypeCode.PICK_INTEGRATE.value and task.deliver_factory_code not in crs.sys_data.store_layer_dict:
            logger.warning('[PC] 发货任务过滤 交付对象不存在 task: %s, factory_code: %s', task.task_id,
                           task.deliver_factory_code)
            continue
        task_list.append(task)
    return task_list


def task_key(task_source, task_id) -> str:
    """
    任务Key
    任务类型_任务ID
    :param task_source:
    :param task_id:
    :return:
    """
    return f'{task_source}_{task_id}'


class Result(object):
    done_num: int = 0
    undone_num: int = 0
    saved_num: int = 0

    def __init__(self, done_num: int, undone_num: int, saved_num: int) -> None:
        self.done_num = done_num
        self.undone_num = undone_num
        self.saved_num = saved_num

    @property
    def all_num(self):
        return self.undone_num + self.done_num

    def to_dict(self):
        return {'all_num': self.all_num,
                'finished_num': self.done_num,
                'unfinished_num': self.undone_num,
                'saved_num': self.saved_num}


def match_wavelet_orderid(queue_list: list, logger: Logger = None) -> tuple[
    list[KwmsWorkQueue], list[tuple[datetime.datetime, datetime.datetime]]]:
    """
    匹配小波次时间
    """
    # 设置小波次时间
    queue_list_2 = [_ for _ in queue_list if _.plan_begin]
    no_match_queues = []
    raw_order_date_list = []
    if queue_list_2:
        date_list = list(set([_.plan_begin for _ in queue_list]))
        wos = WaveletOrderService()
        # 未严格校验 排程日期，worker日期未严格校验仅匹配时间
        waleid_list, order_date_list, raw_order_date_list = wos.get_data(date_list,
                                                                         [_.plan_begin for _ in queue_list_2])
        if logger:
            debug_msg = f'[PC] 小波次 计划时间表: {order_date_list}'
            logger.debug(debug_msg)
        for idx, q in enumerate(queue_list_2):
            q.wops_wavelet_orderid = waleid_list[idx]
            if not q.wops_wavelet_orderid:
                no_match_queues.append(q)
                if logger:
                    warning_msg = f'[PC] ({q.command_group_code}){q.task_id} 匹配不到小波次时间 plan_begin: {date_str(q.plan_begin)}'
                    logger.warning(warning_msg)
    return no_match_queues, raw_order_date_list


def process_work_to_queue(processed_task_list: list[Task], raw_task_dict: dict, logger: Logger) -> tuple[
    list[KwmsWorkQueue], int, int]:
    """
    将Task 转换为 Queue
    """
    # 预处理一下 多DC但需要栈板直发的场景
    for pt in processed_task_list:
        # 多DC
        if (pt.is_master_dc_task and
                # 最终包规为 栈板
                (pt.target_pack_type == PackType.ZB.value or
                 # PTX
                 pt.ext_ptx or
                 # 纸箱直发场景
                 (pt.origin_pack_type == pt.target_pack_type == PackType.ZX.value))):
            for ot in pt.ext_group_tasks[1:]:
                ot: Task
                if ot.run_status != TaskStatus.DONE:
                    info_msg = f'[PC] task: {ot.task_id} 随主任务一并 处理 主任务: {pt.task_id}'
                    logger.info(info_msg)
                ot.run_status = pt.run_status
                if ot.run_status == TaskStatus.DONE:
                    info_msg = f'[PC] task: {ot.task_id} 随主任务一并 完成 主任务: {pt.task_id}'
                    logger.info(info_msg)
                    copy_plist = copy.deepcopy(pt.process_list)  # 多DC场景 以act_num为准
                    ot.process_list = copy_plist
        # 多DC
        if pt.is_master_dc_task and (
                # 开始包规为 3.自制箱 且 最终包规为 3.自制箱
                (pt.origin_pack_type == PackType.ZZX.value and pt.target_pack_type == PackType.ZZX.value)
                or
                # 多DC 主任务已完成，同LPN其他任务未处理场景
                (pt.run_status == TaskStatus.DONE and not pt.ext_group_tasks[-1].process_list[-1].device_type)
        ):
            for p in pt.process_list:
                if p.device_type == DeviceType.GTP.value:
                    p.origin_sku_num = pt.origin_sku_num
                    break
                p.set_origin_sku_num(pt.origin_sku_num)
            for ot in pt.ext_group_tasks[1:]:
                ot: Task
                ot.run_status = pt.run_status
                ot.ext_route = pt.ext_route
                if ot.run_status == TaskStatus.DONE:
                    copy_plist = copy.deepcopy(pt.process_list)
                    pass_gtp = False
                    for cp in copy_plist:
                        if cp.device_type == DeviceType.GTP.value:
                            cp.origin_sku_num = ot.origin_sku_num
                            cp.target_sku_num = ot.target_sku_num
                            pass_gtp = True
                            continue
                        if pass_gtp:
                            cp.set_origin_sku_num(ot.target_sku_num)
                        else:
                            cp.set_origin_sku_num(ot.origin_sku_num)
                    ot.process_list = copy_plist
    # 针对多DC任务，修正OriginSkuNum 不再使用ActNum (最终修正, 无论之前是否修正过)
    for pt in processed_task_list:
        rt = raw_task_dict.get(task_key(pt.ext_source.value, pt.task_id))
        # 针对多DC任务 (已完成)
        if pt.origin_sku_num < pt.ext_act_num and pt.run_status == TaskStatus.DONE:
            for p in pt.process_list:
                # 只有在这些设备之前 才会保持原origin_sku_num    ①GTP拣选 ②ZBJL栈板拣料
                if p.device_type in (DeviceType.GTP.value, DeviceType.ZBJL.value):
                    p.origin_sku_num = rt.origin_sku_num
                    # 在库、发货 任务
                    if pt.ext_source in (TaskSource.INTERIOR, TaskSource.DELIVERY):
                        p.target_sku_num = rt.target_sku_num
                    break
                p.set_origin_sku_num(rt.origin_sku_num)
        if DeviceType.ZHC.value in [p.device_type for p in pt.process_list] and not pt.ext_ptx:
            # ZHC
            pass_zhc = False
            for p in pt.process_list:
                if pass_zhc:
                    p.set_origin_sku_num(rt.target_sku_num)
                    continue
                # ZHC 除PTX外  其余在经过该设备时，会修改PCS数
                if p.device_type in (DeviceType.ZHC.value,):
                    pass_zhc = True
                    p.origin_sku_num = rt.origin_sku_num
                    p.target_sku_num = rt.target_sku_num
                    continue
                p.set_origin_sku_num(rt.origin_sku_num)

    #
    queue_list = []
    s_delivery_list = []
    done_task_size = len([_ for _ in processed_task_list if _.run_status == TaskStatus.DONE])
    undone_task_size = len([_ for _ in processed_task_list if _.run_status != TaskStatus.DONE])
    #
    for t in processed_task_list:
        task_source = t.ext_source
        work_id = task_key(task_source.value, t.task_id)
        raw_task = raw_task_dict.get(work_id)
        if t.run_status != TaskStatus.DONE:
            step = t.process.ext_step if t.process else None
            if step:
                warning_msg = (
                    f'[PC] 任务未完成 task: {t.task_id}, 状态: {t.run_status}, 路径: ({t.ext_route.id}){t.ext_route.name}， '
                    f'当前状态(将要执行): 子步骤: {step.id}-{step.seq}, 设备类型: {step.ext_equ_type}, 最后遗言: {t.ext_last_info}')
                logger.warning(warning_msg)
            else:
                warning_msg = (
                    f'[PC] 任务未完成 task: {t.task_id}, 状态: {t.run_status}, 路径: ({t.ext_route.id}){t.ext_route.name}， '
                    f'当前状态(将要执行): None, 最后遗言: {t.ext_last_info}')
                logger.warning(warning_msg)
            continue
        s_delivery_list.append(t)
        # 主任务
        work = KwmsWorkQueue(raw_task.__dict__)
        work.task_id = work_id
        work.state = 2
        # 多条路径时，存储较长(GTP拣选)路径
        work.wops_command_unit_routeid = t.ext_route.id if not t.ext_route_2 else t.ext_route_2.id
        # 多DC
        work.group_id = t.ext_factory_group_id
        # 部分修正最终包规场景
        work.target_pack_type = t.target_pack_type
        # 在库任务 设置订单ID
        if work.command_group_code == TaskSource.INTERIOR.value:
            work.orderdetil = t.cwmsinvtaskdetil
        # 子过程
        pool_list = []
        for idx, s in enumerate(t.process_list):
            sp: TaskProcess = s
            pool = KwmsWorkPool()
            pool.sub_task_id = f'{work_id}_{idx + 1}'
            if idx != 0:
                pool.parent_sub_id = f'{work_id}_{idx}'
            else:
                pool.parent_sub_id = ''
            pool.sku = work.sku
            pool.equ_type = sp.device_type
            pool.equ_code = sp.device_code
            pool.origin_work_loc = sp.begin_pos
            pool.target_work_loc = sp.end_pos
            if idx == 0:
                pool.origin_lpn = work.lpn
                pool.target_lpn = work.lpn
            else:
                pool.origin_lpn = pool_list[-1].target_lpn
                pool.target_lpn = sp.ext_target_lpn if sp.ext_target_lpn else pool.origin_lpn
            pool.origin_pack_type = sp.origin_pack_type
            pool.target_pack_type = sp.target_pack_type
            pool.origin_sku_num = sp.origin_sku_num
            pool.target_sku_num = sp.target_sku_num
            pool.operate_pack_type = sp.operate_pack_type
            pool.operate_pack_number = sp.operate_pack_num
            # 状态
            pool.state = 2
            pool.plan_begin = ts_date(sp.begin_ts)
            pool.plan_end = ts_date(sp.end_ts)
            pool.time_cost = sp.end_ts - sp.begin_ts
            pool.wops_work_resourceid = sp.ext_step.wops_work_resourceid
            pool_list.append(pool)
        # 若存在 子任务拆分情况 特殊处理一下
        if t.ext_sub_task_list:
            subt_parent_sub_id = pool_list[-1].sub_task_id
            subt_parent_sub_idx = len(pool_list)
            for idx, subt in enumerate(t.ext_sub_task_list):
                subt: Task
                sub_plist = subt.process_list[len(t.process_list):]
                for idx_p, sub_p in enumerate(sub_plist):
                    sub_p: TaskProcess
                    pool = KwmsWorkPool()
                    pool.sub_task_id = f'{work_id}_{subt_parent_sub_idx + idx_p + 1}_{idx}'
                    if idx_p != 0:
                        pool.parent_sub_id = f'{work_id}_{subt_parent_sub_idx + idx_p}_{idx}'
                    else:
                        pool.parent_sub_id = subt_parent_sub_id
                    pool.sku = work.sku
                    pool.equ_type = sub_p.device_type
                    pool.equ_code = sub_p.device_code
                    pool.origin_work_loc = sub_p.begin_pos
                    pool.target_work_loc = sub_p.end_pos
                    if idx_p != 0:
                        pool.origin_lpn = pool_list[-1].target_lpn
                        pool.target_lpn = sub_p.ext_target_lpn if sub_p.ext_target_lpn else pool.origin_lpn
                    else:
                        pool.origin_lpn, pool.target_lpn = subt.lpn, subt.lpn
                    pool.origin_pack_type = sub_p.origin_pack_type
                    pool.target_pack_type = sub_p.target_pack_type
                    pool.origin_sku_num = sub_p.origin_sku_num
                    pool.target_sku_num = sub_p.target_sku_num
                    pool.operate_pack_type = sub_p.operate_pack_type
                    pool.operate_pack_number = sub_p.operate_pack_num
                    # 状态
                    pool.state = 2
                    pool.plan_begin = ts_date(sub_p.begin_ts)
                    pool.plan_end = ts_date(sub_p.end_ts)
                    pool.time_cost = sub_p.end_ts - sub_p.begin_ts
                    pool.wops_work_resourceid = sub_p.ext_step.wops_work_resourceid
                    pool_list.append(pool)
        work.ext_pool_list = pool_list
        # 设置母任务开始结束时间 (可能存在没有子任务场景，此时不设置计划时间)
        if pool_list:
            work.plan_begin = pool_list[0].plan_begin
            # 有子任务场景
            if t.ext_sub_task_list:
                sorted_pool_list = sorted([_.process_list[-1].end_ts for _ in t.ext_sub_task_list])
                work.plan_end = ts_date(sorted_pool_list[-1])
            else:
                work.plan_end = pool_list[-1].plan_end
        queue_list.append(work)
    return queue_list, done_task_size, undone_task_size


def last_process(queue_list: list[KwmsWorkQueue], done_task_size: int, undone_task_size: int,
                 taskids_tuple: tuple[list[str], list[str], list[str], list[str]], logger: Logger) -> Result:
    """
    后处理
    - 补充完善任务数据
    - 将数据回写至数据库
    :param logger:
    :param queue_list:
    :param taskids_tuple:
    :param done_task_size:
    :param undone_task_size:
    :return:
    """
    # queue_list, done_task_size, undone_task_size = process_work_to_queue(processed_task_list, raw_task_dict, logger)
    # 匹配小波次时间
    # match_wavelet_orderid(queue_list, logger)
    # 存数据库
    saved_queue_list = KwmsService().save_data_v2(queue_list, logger)
    # 修改母任务状态
    DeliveryWorkService().update_data_v2(saved_queue_list, taskids_tuple, logger)
    #
    saved_queue_size = len(saved_queue_list)
    info_msg = f'[PC] (有效)总任务: {done_task_size + undone_task_size}, 已完成: {done_task_size}, 未完成: {undone_task_size}, 已保存: {saved_queue_size}'
    logger.info(info_msg)
    return Result(done_task_size, undone_task_size, saved_queue_size)


def gen_port_factory_pcs_dict(task_list: list[Task], equ_types: set[int], logger: Logger) -> dict[str, dict[str, int]]:
    """
    出货口 交付对象+PCS数 数据整理

    :param task_list:
    :param equ_types: 出货口类型Code
    :param logger:
    :return:
    """
    if not equ_types:
        return {}

    # 交付对象信息
    factory_dict = dict(zip(equ_types, [{} for _ in range(len(equ_types))]))

    # 获取work_date
    work_date_list = []
    assigned_factory_ports_dict: dict[str, int] = {}
    for t in task_list:
        if t.ext_source == TaskSource.DELIVERY and t.process_list:
            if t.vehicle_task_begin_time:
                work_date = t.vehicle_task_begin_time.strftime('%Y-%m-%d')
                work_date_tomorrow = (t.vehicle_task_begin_time + datetime.timedelta(days=1)).strftime('%Y-%m-%d')
                work_date_list = [work_date, work_date_tomorrow]
                break
    if work_date_list:
        # 交付对象->分配的出货口 数量
        assigned_factory_ports_dict = VehicleTaskService().get_assigned_ports_dict(work_date_list)
    #
    for t in task_list:
        # 非发货任务 忽略
        if t.ext_source != TaskSource.DELIVERY:
            continue
        # 任务 无步骤, 跳过
        if not t.process_list:
            continue
        # 非出货口 忽略
        if not t.ext_route.ext_ch_port:
            continue
        device_type = t.ext_route.ext_ch_port
        # 分别统计
        factory_code = t.factory_code()
        if factory_code not in factory_dict[device_type]:
            if factory_code in assigned_factory_ports_dict:
                port_nums = assigned_factory_ports_dict[factory_code]
                if port_nums < 1:
                    warning_msg = f'[PC] {device_type} 出货口分配 factory_code: {factory_code} 分配出货口数量: 0, 修正为: 1'
                    logger.warning(warning_msg)
                    port_nums = 1
            else:
                warning_msg = f'[PC] {device_type} 出货口分配 factory_code: {factory_code} 未找到发车任务, 修正为: 1'
                logger.warning(warning_msg)
                port_nums = 1
            # 记录 [PCS数] [发车时间] [出货口分配数量] [是否急料]，[可用的出货口(设备)IDs] 用于不同的出货口分配策略
            factory_dict[device_type][factory_code] = {
                'pcs': t.target_sku_num,
                'vehicle_begin': t.vehicle_task_begin_time,
                'port_nums': port_nums,
                'urgent': t.is_urgent,
                'equid_list': t.ext_route.ext_ch_port_ids if t.ext_route.ext_ch_port_ids else []
            }
        else:
            factory_dict[device_type][factory_code]['pcs'] += t.target_sku_num

    info_msg = f'[PC] 交付口及对象分析(交付口隔离) --> 交付口及对象PCS数：{factory_dict}'
    logger.info(info_msg)
    return factory_dict


def gen_zk_port_factory_pcs_dict(task_list: list[Task], equ_types: set[int], inv_isolated_task: list[str],
                                 logger: Logger) -> dict[
    str, dict[str, dict]]:
    """
    在库任务 出货口及PCS数统计
    """
    if not equ_types:
        return {}

    # 交付对象信息
    factory_port = dict(zip(equ_types, [{} for _ in range(len(equ_types))]))

    for t in task_list:
        # 非在库任务 直接跳过
        if t.ext_source != TaskSource.INTERIOR:
            continue
        if not t.process_list:
            continue
        # 仅部分任务会涉及 出货口分配 (有些可能虽然使用出货口，但无需进行出货隔离)
        if t.ext_route.command_type_code not in inv_isolated_task:
            continue
        # 绑定的订单号, 指定出货口
        cwmsinvtaskdetil, consigneekey = t.cwmsinvtaskdetil, t.consigneekey
        if not cwmsinvtaskdetil:
            t.ext_last_info = '在库出货口隔离任务 无绑定订单号'
            continue

        # 出货口
        equ_type = t.ext_zk_equ_type
        if equ_type not in factory_port:
            factory_port[equ_type] = {}
        # 符合条件的 同订单
        if consigneekey not in factory_port[equ_type]:
            factory_port[equ_type][consigneekey] = {}
        if cwmsinvtaskdetil not in factory_port[equ_type][consigneekey]:
            factory_port[equ_type][consigneekey][cwmsinvtaskdetil] = {
                'pcs': 0, 'values': 0
            }
        factory_port[equ_type][consigneekey][cwmsinvtaskdetil]['pcs'] += t.target_sku_num
        factory_port[equ_type][consigneekey][cwmsinvtaskdetil]['values'] += 1
    info_msg = f'[PC] (在库) 交付口及对象分析(交付口隔离) -> 交付口及订单PCS数: {factory_port}'
    logger.info(info_msg)
    return factory_port


def get_urgent_order_add_date_dict(task_list: list[Task]) -> dict[str, datetime]:
    """
    获取 急料的 订单添加时间
    """
    urgent_task_ids = [_.task_id for _ in task_list if _.is_urgent]
    detail_list: list[OrderDetail] = OrderDetailService().order_add_date(urgent_task_ids)
    result = dict()
    for d in detail_list:
        result[d.SERIALKEY] = d.ADDDATE
    # 修正数据
    for t in task_list:
        if t.task_id not in result:
            result[t.task_id] = 0
    return result


def gen_base_time(task_list: list[Task], base_datetime: str, logger: Logger) -> float:
    """
    获取模拟 基准时间
    """
    source = None
    base_time = None
    if base_datetime:
        base_time = datetime.datetime.strptime(base_datetime, '%Y-%m-%dT%H:%M:%S')
        if base_time:
            source = f'Web入参: {base_datetime}'
    if not base_time:
        base_time, wavelet_id = WaveletOrderService().get_base_time()
        if base_time:
            source = f'波次匹配: {wavelet_id}'
    if not base_time:
        if task_list:
            # 基准时间 取发车任务最早的任务时间点
            first_task = task_list[0]
            # 在库任务无发车时间，使用时间窗时间做基准时间
            base_time = first_task.vehicle_task_begin_time if first_task.vehicle_task_begin_time else first_task.time_window_start_time
            if base_time:
                source = f'母任务: {first_task.ext_source} {first_task.task_id}'
    debug_msg = f'[PC] INIT 基准时间: {base_time.strftime("%Y-%m-%d %H:%M:%S") if base_time else str(0)}, 来源: {source}'
    logger.debug(debug_msg)
    return base_time.timestamp() if base_time else float(0)


def solve_ptx(task_list: list[Task], sys_data: SysDataCtx, logger) -> list[Task]:
    """
    处理PTX 若有的话
    """
    ptx_task_list = [_ for _ in task_list if _.ext_ptx]
    #
    base_time = sys_data.base_time
    sim_time = base_time + sys_data.sim_time_limit
    # 当 有PTX场景时
    if ptx_task_list:
        # 非PTX的栈板任务 (排除自制)
        noptx_task_list = [_ for _ in task_list if
                           not _.ext_ptx and _.origin_pack_type == PackType.ZB.value and _.ext_source not in (
                               TaskSource.INTERIOR,)]
        #
        c_ptx_sys_data = copy.copy(sys_data)
        c_noptx_sys_data = copy.copy(sys_data)
        c_ptx_task_list = copy.deepcopy(ptx_task_list)
        c_noptx_task_list = copy.deepcopy(noptx_task_list)
        info_msg = f'[PC] 有PTX场景 PTX数量: {len(c_ptx_task_list)}, 非PTX栈板数量: {len(c_noptx_task_list)}'
        logger.info(info_msg)
        run_task_list(c_noptx_task_list, c_ptx_sys_data, logger)
        run_task_list(c_ptx_task_list, c_noptx_sys_data, logger)
        # 仅需考虑 普通任务 或者多DC下的Master任务
        fix_ptx_task_list = [_ for _ in c_ptx_task_list if not _.ext_group_tasks or _.is_master_dc_task]
        fix_ptx_task_sorted = sorted([_ for _ in fix_ptx_task_list if _.vehicle_task_begin_time],
                                     key=lambda _: _.vehicle_task_begin_time.timestamp())
        fix_ptx_task_notime = [_ for _ in fix_ptx_task_list if not _.vehicle_task_begin_time]
        fix_ptx_task_list = fix_ptx_task_sorted + fix_ptx_task_notime  # 对PTX任务按发车排序
        # 统计一下空闲时间 (将耗时时间细化为 DDJ堆垛机的时间)
        # 栈板 按巷道分发PTX任务; 按巷道计算耗时
        for lane in sys_data.get_zb_lane_data().keys():
            inuse_times_list = ([(base_time, base_time)] +
                                [_.cd_time() for _ in c_noptx_task_list if _.cd_time() and _.begin_pos.lane == lane] +
                                [(sim_time, sim_time)])
            # 当前巷道空闲时间段
            free_times_list = find_free_time(inuse_times_list)  # 空闲时间段
            debug_msg = f'[PC] 栈板 {lane} 任务空闲时间段: {[(ts_str(_[0]), ts_str(_[1])) for _ in free_times_list]}'
            logger.debug(debug_msg)
            if free_times_list:
                c_free_times_list = [list(_) for _ in free_times_list]
                task_time = {}
                for pt in [_ for _ in fix_ptx_task_list if _.begin_pos.lane == lane]:
                    if pt.run_status != TaskStatus.DONE:
                        continue
                    cost = pt.cd_cost()
                    for ft in c_free_times_list:
                        if cost <= (ft[1] - ft[0]):
                            task_time[pt.task_id] = ft[0]
                            ft[0] += cost
                            break
                for pt in ptx_task_list:
                    if pt.begin_pos.lane == lane:
                        pt.ext_ptx_time = task_time.get(pt.task_id)
                        debug_msg = f'[PC] PTX 任务 {pt.task_id} lane: {pt.begin_pos.lane} 分配时间: {ts_str(pt.ext_ptx_time)}'
                        logger.debug(debug_msg)
    # 若没有在库任务，直接返回
    zk_task_list = [_ for _ in task_list if _.ext_source == TaskSource.INTERIOR]
    if not zk_task_list:
        return run_task_list(task_list, sys_data, logger)
    else:
        for zk in zk_task_list:
            # 立即执行这些任务
            zk.ext_reassign_time = 1

    # 处理在库任务
    no_zk_task_list = [_ for _ in task_list if _.ext_source not in (TaskSource.INTERIOR,)]
    #
    c_zk_sys_data = copy.copy(sys_data)
    c_nozk_sys_data = copy.copy(sys_data)
    c_zk_task_list = copy.deepcopy(zk_task_list)
    c_nozk_task_list = copy.deepcopy(no_zk_task_list)
    run_task_list(c_zk_task_list, c_zk_sys_data, logger)
    run_task_list(c_nozk_task_list, c_nozk_sys_data, logger)
    # 统计一下 各巷道的 [堆垛机 / 穿梭车] 的使用耗时
    for lane in list(sys_data.get_zb_lane_data().keys()) + list(sys_data.get_jx_lane_data().keys()):
        inuse_times_list = ([(base_time, base_time)] +
                            [_.cd_time() for _ in c_nozk_task_list if _.cd_time() and _.begin_pos.lane == lane] +
                            [(sim_time, sim_time)])
        free_times_list = find_free_time(inuse_times_list)
        if free_times_list:
            c_free_times_list = [list(_) for _ in free_times_list]
            task_time = {}
            for pt in [_ for _ in c_zk_task_list if _.begin_pos.lane == lane]:
                if pt.run_status != TaskStatus.DONE:
                    continue
                cost = pt.cd_cost() if pt.cd_cost() is not None else pt.ext_group_tasks[0].cd_cost()
                for ft in c_free_times_list:
                    if cost <= (ft[1] - ft[0]):
                        task_time[pt.task_id] = ft[0]
                        ft[0] += cost
                        break
            for pt in zk_task_list:
                if pt.begin_pos.lane == lane:
                    pt.ext_reassign_time = task_time.get(pt.task_id)
                    debug_msg = f'[PC] 在库 任务 {pt.task_id} lane: {pt.begin_pos.lane} 分配时间: {ts_str(pt.ext_reassign_time)}'
                    logger.debug(debug_msg)
    #
    return run_task_list(task_list, sys_data, logger)


def solve_wavelet_orderid(task_list: list[Task], sys_data: SysDataCtx, logger: Logger) -> tuple[
    list[KwmsWorkQueue], int, int]:
    """
    处理 小波次
    """
    copied_task_list = copy.deepcopy(task_list)
    copied_sys_data = copy.copy(sys_data)
    #
    fixed_task_list = solve_ptx(copied_task_list, copied_sys_data, logger)
    queue_list, done_size, undone_size = process_work_to_queue(fixed_task_list, sys_data.raw_task_dict, logger)
    no_match_queues, order_date_list = match_wavelet_orderid(queue_list)
    msg = f'[PC] 小波次 未匹配到的任务数: %s', len(no_match_queues)
    logger.info(msg)

    # 当有未匹配到小波次场景, 不断尝试将计划开始时间设置为 最近的下一波次开始时间
    idx = 0
    reassign_task_date: dict[str, datetime.datetime] = dict()
    reassign_queue_dict = {}
    while no_match_queues:
        if idx >= 3:  # 小波次调整 最大尝试次数: 3
            logger.warning('[PC] 小波次调整 超过最大尝试次数，终止执行。')
            break
        idx += 1
        #
        no_match_queue_ids = [f'{_.command_group_code}_{_.task_id}' for _ in no_match_queues]
        #
        for nmq in no_match_queues:
            nmq: KwmsWorkQueue
            has_find = False
            for od in order_date_list:
                # 查找最近的下一个波次
                last_reassign_date = reassign_task_date[
                    nmq.task_id] if nmq.task_id in reassign_task_date else nmq.plan_begin
                if last_reassign_date < od[0]:
                    reassign_task_date[nmq.task_id] = od[0]
                    has_find = True
                    break
            if not has_find:
                error_msg = f'[PC] 小波次调整 无法匹配小波次 task: {nmq.task_id} plan_time: {date_str(last_reassign_date)}'
                logger.error(error_msg)
        if not reassign_task_date:
            logger.warning('[PC] 小波次调整 无法再继续匹配小波次，终止调整')
            break
        # 重新设置计划开始时间
        for q in queue_list:
            key = q.task_id
            if key in reassign_task_date:
                reassign_queue_dict[key] = q
        #
        copied_task_list = copy.deepcopy(task_list)
        copied_sys_data = copy.copy(sys_data)
        for t in copied_task_list:
            key = f'{t.ext_source.value}_{t.task_id}'
            if key in reassign_task_date:
                q = reassign_queue_dict.get(key)
                if f'{q.command_group_code}_{q.task_id}' in no_match_queue_ids:
                    debug_msg = f'[PC] 小波次调整 task:{t.task_id} ({date_str(q.plan_begin)} - {date_str(q.plan_end)}) 无法匹配小波次，重新分配开始执行时间: {reassign_task_date[key]}'
                    logger.debug(debug_msg)
                t.ext_reassign_time = reassign_task_date[key].timestamp()
        # 重新排一次计划
        logger.warning('[PC] 有小波次未成功匹配场景，进行重排操作: 次数 -> %s', str(idx))
        fixed_task_list = solve_ptx(copied_task_list, copied_sys_data, logger)
        queue_list, done_size, undone_size = process_work_to_queue(fixed_task_list, copied_sys_data.raw_task_dict,
                                                                   logger)
        no_match_queues, order_date_list = match_wavelet_orderid(queue_list)
        msg = f'[PC] 小波次 未匹配到的任务数: %s', len(no_match_queues)
        logger.info(msg)

    return queue_list, done_size, undone_size


def parse_work_station_info(work_station_info: list[WorkStationInfo]) -> dict:
    """
    解析 WorkStationInfo
    """
    work_station_dict = {}
    for ws in work_station_info:
        equ_type = ws.equType
        if equ_type not in work_station_dict:
            work_station_dict[equ_type] = {}
        for wt in ws.workTypeList:
            if wt not in work_station_dict[equ_type]:
                work_station_dict[equ_type][wt] = []
            # 追加 可用设备code
            work_station_dict[equ_type][wt].append(ws.equCode)
    #
    return work_station_dict


def solve(delivery_ids: list[str], interior_ids: list[str], prework_ids: list[str], tally_ids: list[str],
          pending_ids: list[str], all_equ_info: list[EquInfo], work_station_info: list[WorkStationInfo],
          base_datetime: str, storer_layer_dict: dict[str, StorerDeliverLayer], logger: Logger) -> Result:
    """
    执行任务
    :param delivery_ids: 发货任务
    :param interior_ids: 在库任务
    :param prework_ids: 预作业任务
    :param tally_ids: 理货任务
    :param pending_ids: 需锁定任务
    :param all_equ_info: 所有的
    :param work_station_info: 设备可执行的任务 工作类型
    :param base_datetime: 基准时间 '2024-03-05T09:51:29'
    :param storer_layer_dict: 商
    :param logger: 日志
    :return:
    """
    # 任务列表
    raw_task_list = []
    raw_task_dict = {}
    # 获取原始任务数据
    if delivery_ids:
        dws = DeliveryWorkService()
        raw_task_list += dws.get_work_queue(delivery_ids)
    if interior_ids:
        iws = InteriorWorkService()
        raw_task_list += iws.get_work_queue(interior_ids)
    if prework_ids:
        pws = PreworkWorkService()
        raw_task_list += pws.get_work_queue(prework_ids)
    if tally_ids:
        tws = TallyWorkService()
        raw_task_list += tws.get_work_queue(tally_ids)
    # 任务数据重新排序
    # FIXME: 交付作业优先级最高 在保证交付的情况下 还有设备空闲 你要选在库作业进来做；在库作业分为 有站台触发的 还有 自动下发的；
    nodelivery_task_list = [_ for _ in raw_task_list if _.command_group_code != 'deliver']
    delivery_task_list = [_ for _ in raw_task_list if _.command_group_code == 'deliver']
    sorted_task_list = sorted(delivery_task_list, key=lambda t: t.vehicle_task_begin_time) + nodelivery_task_list
    # 原始任务 备份
    loc_list = []
    for t in sorted_task_list:
        key = task_key(t.command_group_code, t.task_id)
        raw_task_dict[key] = t
        loc_list.append(t.origin_loc)
    # 修正
    # 获取一些配置数据
    sys_data = get_sys_init_data(loc_list, logger)
    sys_data.raw_task_dict = raw_task_dict
    # 整理下 初始化数据
    init_equ_map: dict[str, EquInfo] = dict()
    for ei in all_equ_info:
        init_equ_map[ei.uk] = ei
    sys_data.init_equ_dict = init_equ_map
    # 设备工作类型限制
    work_station_dict = parse_work_station_info(work_station_info)
    logger.debug('[INIT] 设备工作类型限制: %s', work_station_dict)
    sys_data.work_station_dict = work_station_dict
    # 进行适配
    crs = CommandResourceService(sys_data)
    #
    sys_data.crs = crs
    # 交付对象 类别及楼层
    sys_data.store_layer_dict = storer_layer_dict
    # 过滤任务数据 (匹配不到交付对象的数据，暂时过滤出来)
    filtered_task_list = filter_task_list(sorted_task_list, crs, logger)
    # 修正任务数据
    fixed_task_list = fix_task_list(filtered_task_list, crs, logger)
    # (在库)出货口
    logger.debug('[INIT] 在库需按单隔离出口的作业: %s', sys_data.inv_isolated_task)
    zk_ports = set()
    for r in crs.route_list:
        if r.ext_zk_ports and r.command_type_code in sys_data.inv_isolated_task:
            zk_ports.update(r.ext_zk_ports)
    sys_data.zk_ports = zk_ports
    # (发货)出货口
    ch_ports = set([_.ext_ch_port for _ in crs.route_list if
                    _.ext_ch_port and _.command_type_code == CommandTypeCode.PICK_INTEGRATE.value])
    sys_data.ch_ports = ch_ports
    # 历史 出货口 交付口分配
    sys_data.prev_factory_port = get_prev_vehicle_port(ch_ports)
    sys_data.zk_prev_factory_port = get_zk_prev_vehicle_port(zk_ports)
    # 历史 出货口 出货量统计
    sys_data.statistical_factory_port = get_statistical_factory_port(ch_ports)
    # 本次 出货口 交付对象PCS数
    sys_data.factory_port_dict = gen_port_factory_pcs_dict(fixed_task_list, ch_ports, logger)  # FIXME：可能需要修改
    sys_data.zk_factory_port_dict = gen_zk_port_factory_pcs_dict(fixed_task_list, zk_ports, sys_data.inv_isolated_task,
                                                                 logger)
    # 急料 订单添加时间
    sys_data.urgent_order_add_date_dict = get_urgent_order_add_date_dict(fixed_task_list)
    # 基准时间
    base_time = gen_base_time(fixed_task_list, base_datetime, logger)
    sys_data.base_time = base_time
    # 工作时间
    sys_data.work_calendar = [_.work_ts() for _ in WorkCalendarService.get_work_calendar(base_time)]
    # 修正锁定子任务计划时间
    KwmsService().fix_pending_task(pending_ids, base_time)
    # 设备锁定信息
    sys_data.pending_devices = KwmsService().get_pending_data(pending_ids)
    # 模拟器 时长限制
    debug_msg = f'[INIT] 模拟器 时长限制: {sys_data.sim_time_limit / 60 / 60} 小时'
    logger.debug(debug_msg)
    # 作业可在发车时间前排入的提前量
    debug_msg = f'[INIT] 作业可在发车时间前排入的提前量: {sys_data.advance_vehicle_time / 60} 分钟'
    logger.debug(debug_msg)
    # 基本数据校验
    if sys_data.time_cost_ratio <= 0:
        warning_msg = f'[PC] 设备运行时长系数 异常: {sys_data.time_cost_ratio}, 终止运行'
        logger.warning(warning_msg)
        processed_task_list, done_task_size, undone_task_size = [], 0, 0
    elif not sys_data.work_calendar:
        warning_msg = f'[PC] 工厂日历 异常: {sys_data.work_calendar}, 终止运行'
        logger.warning(warning_msg)
        processed_task_list, done_task_size, undone_task_size = [], 0, 0
    else:
        # 开始执行
        processed_task_list, done_task_size, undone_task_size = solve_wavelet_orderid(fixed_task_list, sys_data, logger)
    # 回写数据
    raw_taskids_tuple = (delivery_ids, interior_ids, prework_ids, tally_ids)
    return last_process(processed_task_list, done_task_size, undone_task_size, raw_taskids_tuple, logger)
